// 19.2 运行时类型识别
/**
 * 运行时类型识别（run-time type identification，RTTI）的功能由两个运算符实现：
 *   · typeid运算符，用于返回表达式的类型。
 *   · dynamic_cast运算符，用于将基类的指针或引用安全地转换成派生类的指针或引用。
 * 当我们将这两个运算符用于某种类型的指针或引用，并且该类型含有虚函数时，运算符将使用指针或引用所绑定对象的动态类型（参见15.2.3节，第534页）。
 * 这两个运算符特别适用于以下情况：我们想使用基类对象的指针或引用执行某个派生类操作并且该操作不是虚函数。一般来说，只要有可能我们应该尽量使用虚函数。
 *   当操作被定义成虚函数时，编译器将根据对象的动态类型自动地选择正确的函数版本。
 * 然而，并非任何时候都能定义一个虚函数。假设我们无法使用虚函数，则可以使用一个RTTI运算符。
 *   另一方面，与虚成员函数相比，使用RTTI运算符蕴含着更多潜在的风险：程序员必须清楚地知道转换的目标类型并且必须检查类型转换是否被成功执行。
 * 使用RTTI必须要加倍小心。在可能的情况下，最好定义虚函数而非直接接管类型管理的重任。
 */

#include <iterator>
#include <vector>
#include <list>
#include <deque>
#include <forward_list>
#include <string>
#include <array>
#include <stack>
#include <queue>
#include <algorithm>
#include <numeric>
using std::swap;
using std::vector, std::list, std::deque, std::forward_list, std::string, std::array, std::stack, std::queue;
#include "../Chapter07/Sales_data.h"
#include <iostream>
using std::begin, std::cbegin, std::end, std::cend, std::find, std::accumulate, std::equal, std::fill, std::fill_n, std::back_inserter;
using std::cin, std::cout, std::endl;
using std::copy, std::replace, std::replace_copy;
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <memory>
#include <new>
using namespace std;
#include <functional>
#include "../VisualStudio2012/15/Quote.h"
#include "../VisualStudio2012/12/TextQuery.h"
#include <tuple>
#include <bitset>
#include <regex>
#include <random>
#include <cmath>
#include <iomanip>
#include <cstdio>
#include <cstdlib>
#include <typeinfo>

int main()
{
    // 19.2.1 dynamic_cast运算符
    /*dynamic_cast运算符（dynamic_cast operator）的使用形式如下所示：[插图]
dynamic_cast<type*>(e)
dynamic_cast<type&>(e)
dynamic_cast<type&&>(e)
      其中，type必须是一个类类型，并且通常情况下该类型应该含有虚函数。在第一种形式中，e必须是一个有效的指针（参见2.3.2节，第47页）；在第二种形式中，e必须是一个左值；在第三种形式中，e不能是左值。
    */

    // 指针类型的dynamic_cast
    /*举个简单的例子，假定Base类至少含有一个虚函数，Derived是Base的公有派生类。如果有一个指向Base的指针bp，则我们可以在运行时将它转换成指向Derived的指针，具体代码如下：[插图]
if(Derived *dp = dynamic_cast<Derived*>(bp))
{
  // 使用dp指向的Derived对象
} else { // bp指向一个Base对象
  // 使用bp指向的Base对象
}
      我们可以对一个空指针执行dynamic_cast，结果是所需类型的空指针。
      值得注意的一点是，我们在条件部分定义了dp，这样做的好处是可以在一个操作中同时完成类型转换和条件检查两项任务。而且，指针dp在if语句外部是不可访问的。
        一旦转换失败，即使后续的代码忘了做相应判断，也不会接触到这个未绑定的指针，从而确保程序是安全的。
      在条件部分执行dynamic_cast操作可以确保类型转换和结果检查在同一条表达式中完成。
    */

    // 引用类型的dynamic_cast
    /*引用类型的dynamic_cast与指针类型的dynamic_cast在表示错误发生的方式上略有不同。因为不存在所谓的空引用，所以对于引用类型来说无法使用与指针类型完全相同的错误报告策略。
        当对引用的类型转换失败时，程序抛出一个名为std：：bad_cast的异常，该异常定义在typeinfo标准库头文件中。
void f(const Base &b)
{
  try {
    const Derived &d = dynamic_cast<const Derived &>(b);
    // 使用b引用的Derived对象
  } catch(bad_cast) {
    // 处理类型转换失败的情况
  }
}
    */

    // 19.2.2 typeid运算符
    /*为RTTI提供的第二个运算符是typeid运算符（typeid operator），它允许程序向表达式提问：你的对象是什么类型？typeid表达式的形式是typeid（e），其中e可以是任意表达式或类型的名字。
        typeid操作的结果是一个常量对象的引用，该对象的类型是标准库类型type_info或者type_info的公有派生类型。type_info类定义在typeinfo头文件中，19.2.4节（第735页）将介绍更多关于type_info的细节。
      typeid运算符可以作用于任意类型的表达式。和往常一样，顶层const（参见2.4.3节，第57页）被忽略，如果表达式是一个引用，则typeid返回该引用所引对象的类型。
        不过当typeid作用于数组或函数时，并不会执行向指针的标准类型转换（参见4.11.2节，第143页）。也就是说，如果我们对数组a执行typeid（a），则所得的结果是数组类型而非指针类型。
      当运算对象不属于类类型或者是一个不包含任何虚函数的类时，typeid运算符指示的是运算对象的静态类型。而当运算对象是定义了至少一个虚函数的类的左值时，typeid的结果直到运行时才会求得。
    */

    // 使用typeid运算符
    /*通常情况下，我们使用typeid比较两条表达式的类型是否相同，或者比较一条表达式的类型是否与指定类型相同：[插图][插图]
Derived *dp = new Derived;
Base *bp = dp; // 两个指针都指向Derived对象
// 在运行时比较两个对象的类型
if (typeid(*dp) == typeid(*bp)) {
  // bp和dp指向同一类型的对象
}
// 检查运行时类型是否是某种指定的类型
if (typeid(*dp) == typeid(Derived)) {
  // bp实际指向Derived对象
}
      当typeid作用于指针时（而非指针所指的对象），返回的结果是该指针的静态编译时类型。
      如果p是一个空指针，则typeid（＊p）将抛出一个名为bad_typeid的异常。
    */

    // 19.2.3 使用RTTI
    /*在某些情况下RTTI非常有用，比如当我们想为具有继承关系的类实现相等运算符时（参见14.3.1节，第497页）。对于两个对象来说，如果它们的类型相同并且对应的数据成员取值相同，则我们说这两个对象是相等的。
        在类的继承体系中，每个派生类负责添加自己的数据成员，因此派生类的相等运算符必须把派生类的新成员考虑进来。
      虚函数的基类版本和派生类版本必须具有相同的形参类型（参见15.3节，第537页）。如果我们想定义一个虚函数equal，则该函数的形参必须是基类的引用。
        此时，equal函数将只能使用基类的成员，而不能比较派生类独有的成员。
      要想实现真正有效的相等比较操作，我们需要首先清楚一个事实：即如果参与比较的两个对象类型不同，则比较结果为false。例如，如果我们试图比较一个基类对象和一个派生类对象，则==运算符应该返回false。
        基于上述推论，我们就可以使用RTTI解决问题了。我们定义的相等运算符的形参是基类的引用，然后使用typeid检查两个运算对象的类型是否一致。如果运算对象的类型不一致，则==返回false；
        类型一致才调用equal函数。每个类定义的equal函数负责比较类型自己的成员。这些运算符接受Base&形参，但是在进行比较操作前先把运算对象转换成运算符所属的类类型。
    */

    // 类的层次关系
    /*
class Base {
friend bool operator==(const Base&, const Base&);
public:
  // Base的接口成员
protected:
  virtual bool equal(const Base&) const;
  // Base的数据成员和其他用于实现的成员
};
class Derived : public Base {
public:
  // Derived的其他接口成员
protected:
  bool equal(const Base&) const;
  // Derived的数据成员和其他用于实现的成员
};
    */

    // 类型敏感的相等运算符
    /*
bool operator==(const Base &lhs, const Base &rhs)
{
  // 如果typeid不相同，返回false；否则虚调用equal
  return typeid(lhs) == typeid(rhs) && lhs.equal(rhs);
}
    */

    // 虚equal函数
    /*继承体系中的每个类必须定义自己的equal函数。派生类的所有函数要做的第一件事都是相同的，那就是将实参的类型转换为派生类类型：[插图]
bool Derived::equal(cosnt Base &rhs) const
{
  // 我们清楚这两个类型是相等的，所以转换过程不会抛出异常
  auto r = dynamic_cast<const Derived &>(rhs);
  // 指向比较两个Derived对象的操作并返回结果
}
      上面的类型转换永远不会失败，因为毕竟我们只有在验证了运算对象的类型相同之后才会调用该函数。然而这样的类型转换是必不可少的，执行了类型转换后，当前函数才能访问右侧运算对象的派生类成员。
    */

    // 基类equal函数
    /*
bool Base::equal(const Base &rhs) const
{
  // 执行比较Base对象的操作
}
    */

    // 19.2.4 type_info类
    /*type_info类的精确定义随着编译器的不同而略有差异。不过，C++标准规定type_info类必须定义在typeinfo头文件中，并且至少提供表19.1所列的操作。
表19.1 type_info的操作
t1 == t2      如果type_info对象t1和t2表示相同的类型，则返回true；否则返回false。
t1 != t2      如果type_info对象t1和t2表示相同的类型，则返回false；否则返回true。
t.name()      返回一个C风格字符串，表示类型名字的可打印形式。类型名字的生成方式因系统而异
t1.before(t2) 返回一个bool值，表示t1是否位于t2之前。before所采用的顺序关系是依赖于编译器的
      因为type_info类一般是作为一个基类出现，所以它还应该提供一个公有的虚析构函数。当编译器希望提供额外的类型信息时，通常在type_info的派生类中完成。
        type_info类没有默认构造函数，而且它的拷贝和移动构造函数以及赋值运算符都被定义成删除的（参见13.1.6节，第450页）。
        因此，我们无法定义或拷贝type_info类型的对象，也不能为type_info类型的对象赋值。创建type_info对象的唯一途径是使用typeid运算符。
      type_info类的name成员函数返回一个C风格字符串，表示对象的类型名字。对于某种给定的类型来说，name的返回值因编译器而异并且不一定与在程序中使用的名字一致。
        对于name返回值的唯一要求是，类型不同则返回的字符串必须有所区别。例如：[插图]
  int arr[10];
Derived d;
Base *p = &d;
cout << typeid(42).name() << ", "
     << typeid(arr).name() << ", "
     << typeid(Sales_data).name() << ", "
     << typeid(std::string).name() << ", "
     << typeid(p).name() << ", "
     << typeid(*p).name() << endl;
      在作者的计算机上运行该程序，输出结果如下：i,A10_i,10Sales_data,Ss,P4Base,7Derived
      type_info类在不同的编译器上有所区别。有的编译器提供了额外的成员函数以提供程序中所用类型的额外信息。读者应该仔细阅读你所用编译器的使用手册，从而获取关于type_info的更多细节。
    */

    return 0;
}